﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Diagnostics;
using System.Reflection;
using System.Text;

namespace RedRiver.Framework.Common.Linq{
    /// <summary>
    /// DataContext 助手
    /// </summary>
    public static class DataContextHelper{
        #region CheckTables

        /// <summary>
        /// 检查DataContext 数据表是否存在,如果不存在则 创建这些数据表
        /// </summary>
        /// <param name="context"></param>
        public static void CheckTables(this DataContext context){
            if (context == null){
                throw new ArgumentNullException("context");
            }

            IEnumerable<MetaTable> metaTables = context.Mapping.GetTables();
            Debug.Assert(metaTables != null);

            var schemaSet = new HashSet<string>();
            foreach (MetaTable metaTable in metaTables){
                string createSchemaForTableCommand = SqlBuilder.GetCreateSchemaForTableCommand(metaTable);
                if (!string.IsNullOrEmpty(createSchemaForTableCommand)){
                    schemaSet.Add(createSchemaForTableCommand);
                }
            }
            foreach (string createSchemaCommand in schemaSet){
                string command = string.Format("SELECT Count(*) FROM sys.schemas WHERE name = N'{0}'",
                                               GetSchemaName(createSchemaCommand));
                IEnumerator countQuery = context.ExecuteQuery(typeof (int), command).GetEnumerator();
                if (countQuery.MoveNext()){
                    // Console.WriteLine("createSchemaCommand state: " + countQuery.Current); 
                    if (Convert.ToInt32(countQuery.Current) < 1){
                        context.ExecuteCommand(createSchemaCommand);
                    }
                }
            }
            var sqlBuilder = new StringBuilder();
            foreach (MetaTable metaTable in metaTables){
                string createTableCommand = SqlBuilder.GetCreateTableCommand(metaTable);
                if (!string.IsNullOrEmpty(createTableCommand)){
                    sqlBuilder.AppendFormat(
                        "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{0}') AND type in (N'U'))\r\n",
                        metaTable.TableName);
                    sqlBuilder.AppendLine("BEGIN");
                    sqlBuilder.AppendLine(createTableCommand);
                    sqlBuilder.AppendLine("END");
                }
            }
            foreach (MetaTable metaTable in metaTables){
                foreach (MetaType metaType in metaTable.RowType.InheritanceTypes){
                    foreach (string fkCommand in SqlBuilder.GetCreateForeignKeyCommands(metaType)){
                        if (!string.IsNullOrEmpty(fkCommand)){
                            sqlBuilder.AppendFormat(
                                "IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'{0}') AND parent_object_id = OBJECT_ID(N'{1}')) \r\n",
                                GetFKName(fkCommand), metaTable.TableName);
                            sqlBuilder.AppendLine("BEGIN");
                            sqlBuilder.AppendLine(fkCommand);
                            sqlBuilder.AppendLine("END");
                        }
                    }
                    //"ADD CONSTRAINT {1} FOREIGN KEY"
                    //IF  EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_FieldModel_TableModel]') AND parent_object_id = OBJECT_ID(N'[dbo].[FieldModel]'))
                }
            }
            if (sqlBuilder.Length > 0){
                sqlBuilder.Insert(0, "SET ARITHABORT ON" + Environment.NewLine);
                context.ExecuteCommand(sqlBuilder.ToString());
            }
        }

        private static string GetSchemaName(string schemaCommand){
            //("CREATE SCHEMA {0}"
            return RemoveQuoted(schemaCommand.Substring(schemaCommand.LastIndexOf(' ')).Trim());
        }

        private static string GetFKName(string fkCommand){
            int start = fkCommand.IndexOf("ADD CONSTRAINT ") + "ADD CONSTRAINT ".Length;
            return fkCommand.Substring(start).Split(' ')[0];
        }

        /// <summary>
        /// 移除字符串 两端的中括号
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        private static string RemoveQuoted(string s){
            if (s == null){
                throw new ArgumentNullException("s");
            }
            if (IsQuoted(s)){
                return s.Substring(1, s.Length - 2);
            }
            return s;
        }

        #endregion CheckTables

        /// <summary>
        /// 检查字符串是否被中括号‘[]’包围
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        private static bool IsQuoted(string s){
            if (s == null){
                throw new ArgumentNullException("s");
            }
            if (s.Length < 2){
                return false;
            }
            return (s.StartsWith("[", StringComparison.Ordinal) && s.EndsWith("]", StringComparison.Ordinal));
        }

        #region ApplyColumnProperties

        /// <summary>
        /// 从 <paramref name="sourceObject"/> 复制 属性 到 <paramref name="self"/>,
        /// 要求 <paramref name="self"/> 属性带有 <see cref="ColumnAttribute"/> 声明
        /// 
        /// 对于数据更新来说，self是 连接对象，而sourceObject 是用来传递值的对象，或是反序列化过来的对象
        /// 
        /// 对于 Linq to sql 对象,仅复制 具有
        /// <see cref="ColumnAttribute"> ColumnAttribute</see> 属性的 属性
        /// </summary>
        /// <param name="self"></param>
        /// <param name="sourceObject">属性复制来源</param>
        public static void ApplyColumnProperties(this object self, object sourceObject){
            if (self == null){
                throw new ArgumentNullException("self", "targetObject Or self not is null");
            }
            if (sourceObject == null){
                throw new ArgumentNullException("sourceObject", "targetObject Or self not is null");
            }
            Type selfType = self.GetType();
            Type sourceType = sourceObject.GetType();

            if (selfType.Equals(sourceType)){
                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(selfType);
                foreach (PropertyDescriptor property in properties){
                    if ((!property.IsReadOnly) && (property.Attributes[typeof (ColumnAttribute)] != null)){
                        property.SetValue(self, property.GetValue(sourceObject));
                    }
                }
            } else{
                PropertyDescriptorCollection selfProperties = TypeDescriptor.GetProperties(selfType);
                PropertyDescriptorCollection sourctProperties = TypeDescriptor.GetProperties(sourceType);
                foreach (PropertyDescriptor selfProperty in selfProperties){
                    if ((!selfProperty.IsReadOnly) && (selfProperty.Attributes[typeof (ColumnAttribute)] != null)){
                        PropertyDescriptor sourceProperty = sourctProperties.Find(selfProperty.Name, true);
                        if ((sourceProperty != null) && (sourceProperty.PropertyType.Equals(selfProperty.PropertyType))){
                            selfProperty.SetValue(self, sourceProperty.GetValue(sourceObject));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 从<paramref name="self"/> 复制属性到<paramref name="targetObject"/>
        /// 要求 <paramref name="targetObject"/> 属性带有 <see cref="ColumnAttribute"/> 声明
        /// </summary>
        /// <param name="self"></param>
        /// <param name="targetObject"></param>
        public static void CopyColumnProperties(this object self, object targetObject){
            if (self == null){
                throw new ArgumentNullException("self", "targetObject Or self not is null");
            }
            if (targetObject == null){
                throw new ArgumentNullException("targetObject", "targetObject Or self not is null");
            }
            Type selfType = self.GetType();
            Type targetType = targetObject.GetType();

            if (selfType.Equals(targetType)){
                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(targetType);
                foreach (PropertyDescriptor property in properties){
                    if ((!property.IsReadOnly) && (property.Attributes[typeof (ColumnAttribute)] != null)){
                        property.SetValue(targetObject, property.GetValue(self));
                    }
                }
            } else{
                PropertyDescriptorCollection selfProperties = TypeDescriptor.GetProperties(selfType);
                PropertyDescriptorCollection targetProperties = TypeDescriptor.GetProperties(targetType);
                foreach (PropertyDescriptor targetProperty in targetProperties){
                    if ((!targetProperty.IsReadOnly) && (targetProperty.Attributes[typeof (ColumnAttribute)] != null)){
                        PropertyDescriptor selfProperty = selfProperties.Find(targetProperty.Name, true);
                        if ((selfProperty != null) && (selfProperty.PropertyType.Equals(targetProperty.PropertyType))){
                            targetProperty.SetValue(targetObject, selfProperty.GetValue(self));
                        }
                    }
                }
            }
        }

        #endregion ApplyColumnProperties

        #region Nested type: SqlBuilder

        internal static class SqlBuilder{
            private const BindingFlags AgentBindingFlags =
                BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.InvokeMethod;

            private const string AgentTypeName = "System.Data.Linq.SqlClient.SqlBuilder";

            private static readonly Type InvokeType = typeof (DataContext).Assembly.GetType(AgentTypeName);

            internal static IEnumerable<string> GetCreateForeignKeyCommands(MetaTable metaTable){
                return
                    (IEnumerable<string>)
                    InvokeType.InvokeMember("GetCreateForeignKeyCommands", AgentBindingFlags, null, null,
                                            new object[]{metaTable});
            }

            internal static string GetCreateTableCommand(MetaTable metaTable){
                return
                    (string)
                    InvokeType.InvokeMember("GetCreateTableCommand", AgentBindingFlags, null, null,
                                            new object[]{metaTable});
            }

            internal static string GetCreateSchemaForTableCommand(MetaTable metaTable){
                return
                    (string)
                    InvokeType.InvokeMember("GetCreateSchemaForTableCommand", AgentBindingFlags, null, null,
                                            new object[]{metaTable});
            }

            internal static IEnumerable<string> GetCreateForeignKeyCommands(MetaType type){
                return
                    (IEnumerable<string>)
                    InvokeType.InvokeMember("GetCreateForeignKeyCommands", AgentBindingFlags, null, null,
                                            new object[]{type});
            }
        }

        #endregion
    }
}